package cn.zyjblogs.config.security;

import cn.zyjblogs.config.security.granter.CaptchaTokenGranter;
import cn.zyjblogs.config.security.granter.QrCodeTokenGranter;
import cn.zyjblogs.config.security.granter.SmsCodeTokenGranter;
import cn.zyjblogs.config.security.policy.OauthAuthorizationCodeServices;
import cn.zyjblogs.starter.redis.utils.RedisTemplateHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zhuyijun
 * @version 1.0.0
 * @description 授权服务
 * @date 2022/8/17 17:51
 */
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    private final TokenStore tokenStore;
    private final AuthenticationManager authenticationManager;
    private final JwtAccessTokenConverter accessTokenConverter;
    private final DataSource dataSource;
    private final JwtTokenEnhancer jwtTokenEnhancer;
    private final OauthResponseExceptionTranslator oAuthResponseExceptionTranslator;
    private final RedisTemplateHandler redisTemplateHandler;

    private final UserDetailsService userDetailsService;
    private final ClientDetailsService oauthClientDetailsService;

    /**
     * 令牌端点的安全约束
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许http请求访问以获取token
        security
                //允许匿名访问端点：url:/oauth/token_key
                .tokenKeyAccess("permitAll()")
//                .checkTokenAccess("isAuthenticated()")
                //TODO 待处理令牌访问安全
                //允许匿名访问端点：url:/oauth/check_token
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    /**
     * 客户端详情服务
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails(dataSource));
    }

    /***
     * 令牌访问端点
     * @param endpoints
     * @author zhuyijun
     * @Description
     * @date 17:52
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                //密码模式
//                .authenticationManager(authenticationManager)
                //授权码模式
//                .authorizationCodeServices(authorizationCodeServices())
//                .tokenServices(tokenServices())
                .tokenGranter(tokenGranter(endpoints))
                .accessTokenConverter(accessTokenConverter)
                //允许表单认证
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                //自定义异常处理
                .exceptionTranslator(oAuthResponseExceptionTranslator);
    }

    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        return new TokenGranter() {
            private CompositeTokenGranter delegate;

            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                if (delegate == null) {
                    delegate = getTokenGranters(endpoints);
                }
                return delegate.grant(grantType, tokenRequest);
            }
        };

    }

    /**
     * m
     * 授权模式
     *
     * @param endpoints
     * @return
     */
    private CompositeTokenGranter getTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> list = new ArrayList<>();
        list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new RefreshTokenGranter(tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new AuthorizationCodeTokenGranter(tokenServices(), authorizationCodeServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new ImplicitTokenGranter(tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new ClientCredentialsTokenGranter(tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new QrCodeTokenGranter(tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new CaptchaTokenGranter(redisTemplateHandler, authenticationManager, tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        list.add(new SmsCodeTokenGranter(redisTemplateHandler, userDetailsService, tokenServices(), clientDetails(dataSource), endpoints.getOAuth2RequestFactory()));
        return new CompositeTokenGranter(list);
    }

    /**
     * 令牌管理服务
     *
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //客户端信息服务
        tokenServices.setClientDetailsService(clientDetails(dataSource));

        //是否产生刷新令牌
        tokenServices.setSupportRefreshToken(true);
        //令牌储存策略
        tokenServices.setTokenStore(tokenStore);
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        enhancerChain.setTokenEnhancers(List.of(jwtTokenEnhancer, accessTokenConverter));
        tokenServices.setTokenEnhancer(enhancerChain);
        //令牌默认有效期
        tokenServices.setAccessTokenValiditySeconds(7200);
        //刷新令牌默认有效期3天
        tokenServices.setRefreshTokenValiditySeconds(259200);

        return tokenServices;
    }

    @Bean
    public ClientDetailsService clientDetails(DataSource dataSource) {
//        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        return oauthClientDetailsService;
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        //设置授权码模式的授权码如何存取
        return new OauthAuthorizationCodeServices(redisTemplateHandler);
//        return new JdbcAuthorizationCodeServices(dataSource);
    }

}